﻿Imports System
Imports System.Collections
Imports System.ComponentModel
Imports System.ComponentModel.Design
Imports System.Drawing
Imports System.Globalization
Imports System.Workflow.ComponentModel
Imports System.Workflow.ComponentModel.Compiler
Imports System.Workflow.ComponentModel.Design
Imports System.Workflow.Runtime

Namespace System.Workflow.Activities
    <Designer(GetType(DelayDesigner), GetType(IDesigner)), SRDescription("DelayActivityDescription"), ToolboxItem(GetType(ActivityToolboxItem)), ToolboxBitmap(GetType(DelayActivity), "Resources.Delay.png"), DefaultEvent("InitializeTimeoutDuration"), ActivityValidator(GetType(DelayActivityValidator)), SRCategory("Standard")> _
    Public NotInheritable Class DelayActivity
        Inherits Activity
        Implements IEventActivity, IActivityEventListener([Of] QueueEventArgs)
        ' Events
        <MergableProperty(False), SRCategory("Handlers"), SRDescription("TimeoutInitializerDescription")> _
        Public Custom Event InitializeTimeoutDuration As EventHandler
            AddHandler(ByVal value As EventHandler)
                MyBase.AddHandler(DelayActivity.InitializeTimeoutDurationEvent, value)
            End AddHandler
            RemoveHandler(ByVal value As EventHandler)
                MyBase.RemoveHandler(DelayActivity.InitializeTimeoutDurationEvent, value)
            End RemoveHandler
        End Event

        ' Methods
        Public Sub New()
        End Sub

        Public Sub New(ByVal name As String)
            MyBase.New(name)
        End Sub

        Protected Overrides Function Cancel(ByVal executionContext As ActivityExecutionContext) As ActivityExecutionStatus
            If (executionContext Is Nothing) Then
                Throw New ArgumentNullException("executionContext")
            End If
            If (Not Me.IsInEventActivityMode AndAlso (Me.SubscriptionID <> Guid.Empty)) Then
                DirectCast(Me, IEventActivity).Unsubscribe(executionContext, Me)
            End If
            Return ActivityExecutionStatus.Closed
        End Function

        Protected Overrides Function Execute(ByVal executionContext As ActivityExecutionContext) As ActivityExecutionStatus
            If (executionContext Is Nothing) Then
                Throw New ArgumentNullException("executionContext")
            End If
            If Me.IsInEventActivityMode Then
                Return ActivityExecutionStatus.Closed
            End If
            DirectCast(Me, IEventActivity).Subscribe(executionContext, Me)
            Me.IsInEventActivityMode = False
            Return ActivityExecutionStatus.Executing
        End Function

        Protected NotOverridable Overrides Function HandleFault(ByVal executionContext As ActivityExecutionContext, ByVal exception As Exception) As ActivityExecutionStatus
            If (executionContext Is Nothing) Then
                Throw New ArgumentNullException("executionContext")
            End If
            If (exception Is Nothing) Then
                Throw New ArgumentNullException("exception")
            End If
            Dim status As ActivityExecutionStatus = Me.Cancel(executionContext)
            If (status = ActivityExecutionStatus.Canceling) Then
                Return ActivityExecutionStatus.Faulting
            End If
            Return status
        End Function

        Protected Overrides Sub Initialize(ByVal provider As IServiceProvider)
            MyBase.Initialize(provider)
            MyBase.SetValue(DelayActivity.QueueNameProperty, Guid.NewGuid)
        End Sub

        Protected Overrides Sub OnClosed(ByVal provider As IServiceProvider)
            MyBase.RemoveProperty(DelayActivity.SubscriptionIDProperty)
            MyBase.RemoveProperty(DelayActivity.IsInEventActivityModeProperty)
        End Sub

        Private Sub System.Workflow.Activities.IEventActivity.Subscribe(ByVal parentContext As ActivityExecutionContext, ByVal parentEventHandler As IActivityEventListener([Of] QueueEventArgs)) Implements IEventActivity.Subscribe
            If (parentContext Is Nothing) Then
                Throw New ArgumentNullException("parentContext")
            End If
            If (parentEventHandler Is Nothing) Then
                Throw New ArgumentNullException("parentEventHandler")
            End If
            Me.IsInEventActivityMode = True
            MyBase.RaiseEvent(DelayActivity.InitializeTimeoutDurationEvent, Me, EventArgs.Empty)
            Dim timeoutDuration As TimeSpan = Me.TimeoutDuration
            Dim expiresAt As DateTime = (DateTime.UtcNow + timeoutDuration)
            Dim service As WorkflowQueuingService = parentContext.GetService([Of] WorkflowQueuingService)
            Dim queueName As IComparable = DirectCast(Me, IEventActivity).QueueName
            Dim item As New TimerEventSubscription(DirectCast(queueName, Guid), MyBase.WorkflowInstanceId, expiresAt)
            service.CreateWorkflowQueue(queueName, False).RegisterForQueueItemAvailable(parentEventHandler, MyBase.QualifiedName)
            Me.SubscriptionID = item.SubscriptionId
            Dim parent As Activity = Me
            Do While (Not parent.Parent Is Nothing)
                parent = parent.Parent
            Loop
            DirectCast(parent.GetValue(TimerEventSubscriptionCollection.TimerCollectionProperty), TimerEventSubscriptionCollection).Add(item)
        End Sub

        Private Sub System.Workflow.Activities.IEventActivity.Unsubscribe(ByVal parentContext As ActivityExecutionContext, ByVal parentEventHandler As IActivityEventListener([Of] QueueEventArgs)) Implements IEventActivity.Unsubscribe
            If (parentContext Is Nothing) Then
                Throw New ArgumentNullException("parentContext")
            End If
            If (parentEventHandler Is Nothing) Then
                Throw New ArgumentNullException("parentEventHandler")
            End If
            Dim service As WorkflowQueuingService = parentContext.GetService([Of] WorkflowQueuingService)
            Dim workflowQueue As WorkflowQueue = Nothing
            Try 
                workflowQueue = service.GetWorkflowQueue(Me.SubscriptionID)
            Catch obj1 As Object
            End Try
            If ((Not workflowQueue Is Nothing) AndAlso (workflowQueue.Count <> 0)) Then
                workflowQueue.Dequeue
            End If
            Dim parent As Activity = parentContext.Activity
            Do While (Not parent.Parent Is Nothing)
                parent = parent.Parent
            Loop
            DirectCast(parent.GetValue(TimerEventSubscriptionCollection.TimerCollectionProperty), TimerEventSubscriptionCollection).Remove(Me.SubscriptionID)
            If (Not workflowQueue Is Nothing) Then
                workflowQueue.UnregisterForQueueItemAvailable(parentEventHandler)
                service.DeleteWorkflowQueue(Me.SubscriptionID)
            End If
            Me.SubscriptionID = Guid.Empty
        End Sub

        Private Sub System.Workflow.ComponentModel.IActivityEventListener<System.Workflow.ComponentModel.QueueEventArgs>.OnEvent(ByVal sender As Object, ByVal e As QueueEventArgs) Implements IActivityEventListener([Of] QueueEventArgs).OnEvent
            If (sender Is Nothing) Then
                Throw New ArgumentNullException("sender")
            End If
            If (e Is Nothing) Then
                Throw New ArgumentNullException("e")
            End If
            Dim context As ActivityExecutionContext = [TryCast](sender,ActivityExecutionContext)
            If (context Is Nothing) Then
                Throw New ArgumentException(SR.Error_SenderMustBeActivityExecutionContext, "sender")
            End If
            If (MyBase.ExecutionStatus <> ActivityExecutionStatus.Closed) Then
                Dim service As WorkflowQueuingService = context.GetService([Of] WorkflowQueuingService)
                service.GetWorkflowQueue(e.QueueName).Dequeue
                service.DeleteWorkflowQueue(e.QueueName)
                context.CloseActivity
            End If
        End Sub


        ' Properties
        Private Property IsInEventActivityMode As Boolean
            Get
                Return CBool(MyBase.GetValue(DelayActivity.IsInEventActivityModeProperty))
            End Get
            Set(ByVal value As Boolean)
                MyBase.SetValue(DelayActivity.IsInEventActivityModeProperty, value)
            End Set
        End Property

        Private Property SubscriptionID As Guid
            Get
                Return DirectCast(MyBase.GetValue(DelayActivity.SubscriptionIDProperty), Guid)
            End Get
            Set(ByVal value As Guid)
                MyBase.SetValue(DelayActivity.SubscriptionIDProperty, value)
            End Set
        End Property

        Private ReadOnly Property System.Workflow.Activities.IEventActivity.QueueName As IComparable
            Get
                Return DirectCast(MyBase.GetValue(DelayActivity.QueueNameProperty), IComparable)
            End Get
        End Property

        <TypeConverter(GetType(TimeoutDurationConverter)), SRDescription("TimeoutDurationDescription"), MergableProperty(False)> _
        Public Property TimeoutDuration As TimeSpan
            Get
                Return DirectCast(MyBase.GetValue(DelayActivity.TimeoutDurationProperty), TimeSpan)
            End Get
            Set(ByVal value As TimeSpan)
                MyBase.SetValue(DelayActivity.TimeoutDurationProperty, value)
            End Set
        End Property


        ' Fields
        Public Shared ReadOnly InitializeTimeoutDurationEvent As DependencyProperty = DependencyProperty.Register("InitializeTimeoutDuration", GetType(EventHandler), GetType(DelayActivity))
        Private Shared IsInEventActivityModeProperty As DependencyProperty = DependencyProperty.Register("IsInEventActivityMode", GetType(Boolean), GetType(DelayActivity), New PropertyMetadata(False))
        Private Shared ReadOnly QueueNameProperty As DependencyProperty = DependencyProperty.Register("QueueName", GetType(IComparable), GetType(DelayActivity))
        Private Shared SubscriptionIDProperty As DependencyProperty = DependencyProperty.Register("SubscriptionID", GetType(Guid), GetType(DelayActivity), New PropertyMetadata(Guid.NewGuid))
        Public Shared ReadOnly TimeoutDurationProperty As DependencyProperty = DependencyProperty.Register("TimeoutDuration", GetType(TimeSpan), GetType(DelayActivity), New PropertyMetadata(New TimeSpan(0, 0, 0)))

        ' Nested Types
        Private Class DelayActivityValidator
            Inherits ActivityValidator
            ' Methods
            Public Overrides Function Validate(ByVal manager As ValidationManager, ByVal obj As Object) As ValidationErrorCollection
                Dim errors As New ValidationErrorCollection
                Dim activity As DelayActivity = [TryCast](obj,DelayActivity)
                If (activity Is Nothing) Then
                    Throw New InvalidOperationException
                End If
                If (activity.TimeoutDuration.Ticks < 0) Then
                    errors.Add(New ValidationError(SR.GetString("Error_NegativeValue", New Object() { activity.TimeoutDuration.ToString, "TimeoutDuration" }), &H531))
                End If
                errors.AddRange(MyBase.Validate(manager, obj))
                Return errors
            End Function

        End Class

        Private NotInheritable Class TimeoutDurationConverter
            Inherits TypeConverter
            ' Methods
            Public Overrides Function CanConvertFrom(ByVal context As ITypeDescriptorContext, ByVal sourceType As Type) As Boolean
                Return ((sourceType Is GetType(String)) OrElse MyBase.CanConvertFrom(context, sourceType))
            End Function

            Public Overrides Function CanConvertTo(ByVal context As ITypeDescriptorContext, ByVal destinationType As Type) As Boolean
                Return ((destinationType Is GetType(String)) OrElse MyBase.CanConvertTo(context, destinationType))
            End Function

            Public Overrides Function ConvertFrom(ByVal context As ITypeDescriptorContext, ByVal culture As CultureInfo, ByVal value As Object) As Object
                Dim zero As Object = TimeSpan.Zero
                Dim str As String = [TryCast](value,String)
                If Not String.IsNullOrEmpty(str) Then
                    Try 
                        zero = TimeSpan.Parse(str)
                        If (Not zero Is Nothing) Then
                            Dim span As TimeSpan = DirectCast(zero, TimeSpan)
                            If (span.Ticks < 0) Then
                                Throw New Exception(String.Format(CultureInfo.CurrentCulture, SR.GetString("Error_NegativeValue"), New Object() { value.ToString, "TimeoutDuration" }))
                            End If
                        End If
                    Catch obj1 As Object
                        Throw New Exception(SR.GetString("InvalidTimespanFormat", New Object() { str }))
                    End Try
                End If
                Return zero
            End Function

            Public Overrides Function ConvertTo(ByVal context As ITypeDescriptorContext, ByVal culture As CultureInfo, ByVal value As Object, ByVal destinationType As Type) As Object
                If ((destinationType Is GetType(String)) AndAlso TypeOf value Is TimeSpan) Then
                    Dim span As TimeSpan = DirectCast(value, TimeSpan)
                    Return span.ToString
                End If
                Return MyBase.ConvertTo(context, culture, value, destinationType)
            End Function

            Public Overrides Function GetStandardValues(ByVal context As ITypeDescriptorContext) As StandardValuesCollection
                Dim values As New ArrayList
                values.Add(New TimeSpan(0, 0, 0))
                values.Add(New TimeSpan(0, 1, 0))
                values.Add(New TimeSpan(0, 30, 0))
                values.Add(New TimeSpan(1, 0, 0))
                values.Add(New TimeSpan(12, 0, 0))
                values.Add(New TimeSpan(1, 0, 0, 0))
                Return New StandardValuesCollection(values)
            End Function

            Public Overrides Function GetStandardValuesSupported(ByVal context As ITypeDescriptorContext) As Boolean
                Return True
            End Function

        End Class
    End Class
End Namespace

